package br.edu.unilasalle.simon_test;

import static com.google.common.base.Preconditions.checkNotNull;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.List;

import org.joda.time.DateTime;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.HasInputDevices;
import org.openqa.selenium.interactions.Keyboard;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.util.StringUtils;

import br.edu.unilasalle.simon_test.util.Predicates;
import br.edu.unilasalle.simon_test.util.Utils;

public abstract class AbstractPage {

	@FindBy(tagName = "html")
	protected WebElement html;

	@FindBy(tagName = "body")
	protected WebElement body;

	@FindBy(id = "title")
	protected WebElement title;

	@FindBy(id = "login")
	protected WebElement username;

	@FindBy(id = "senha")
	protected WebElement password;

	@FindBy(id = "btnLogin")
	protected WebElement login;
	
	@FindBy(css = ".navbar-collapse")
	protected WebElement userMenu;
	
	@FindBy(css =  ".close")
	protected WebElement btnFecharTela;
	
	@FindBy(name = "search")
	public WebElement filter;
	
	public static final String BASE_URL = "test.base_url";
	
	protected WebDriver driver;
	
	private String baseUrl;

	public AbstractPage(WebDriver driver) {
		this.driver = driver;
	}

	protected String getBaseUrl() {
		if (baseUrl == null) {
			baseUrl = Utils.getTestProperty(BASE_URL);
		}
		return baseUrl;
	}

	public void open() {
		driver.get(getBaseUrl());
	}

	public void open(String uri) {
		driver.get(getBaseUrl() + uri);
	}
		
	public void openAsAdmin() {
		this.open();
		this.loginAsAdmin();
		this.hasNoScriptErrors();
	}
	
	public void login() {
		try {
			login.click();
			this.waitAjaxComplete();
		} catch (Exception e) {
			System.out.println("Não foi possível logar na aplicação.");
		}
	}
	
	public void loginAs(String user, String pass) {
		this.type(username, user);
		this.type(password, pass);
		this.login();
	}
	
	public void loginAsAdmin() {
		this.loginAs("admin", "admin");
	}
	
	public void isAtLogin() {
		this.isAt("/#/.");
	}

	public void isAt(String uri) {
		this.waitAjaxComplete();
		assertThat("Esta na tela " + uri, driver.getCurrentUrl(), containsString(uri));
	}
	
	public void logout() throws InterruptedException { 
		WebElement lnkLogout = driver.findElement(By.id("lnkLogout"));
		lnkLogout.click();
	}
	
	public void fecharTela() {
		btnFecharTela.click();
	}

	public void type(WebElement element, String value) {
		// Caso nativeEvents esteja ativo, ver o bug abaixo:
		// https://code.google.com/p/selenium/issues/detail?id=391
		element.sendKeys(value);

		// O javascript abaixo é para garantir que o evento "change" será
		// executado no firefox mesmo quando a janela está sem foco
		// https://code.google.com/p/selenium/issues/detail?id=157
		((JavascriptExecutor) driver).executeScript("$(arguments[0]).change();", element);
	}
	
	public void typeMask(WebElement element, String value) {
		// Caso nativeEvents esteja ativo, ver o bug abaixo:
		// https://code.google.com/p/selenium/issues/detail?id=391
		 Keyboard keyboard = ((HasInputDevices) driver).getKeyboard();
		 element.click();
		 element.clear();
         keyboard.sendKeys(value);
         element.click();

		// O javascript abaixo é para garantir que o evento "change" será
		// executado no firefox mesmo quando a janela está sem foco
		// https://code.google.com/p/selenium/issues/detail?id=157
		((JavascriptExecutor) driver).executeScript("$(arguments[0]).change();", element);
	}

	public void typeDate(WebElement element, DateTime value) {
		element.clear();
		type(element, value.toString("dd/MM/yyyy"));
	}
	
	public void selectComboByValue(WebElement combo, String value) {
		((JavascriptExecutor) driver).executeScript("$(arguments[0]).val('" + value
				+ "').trigger('liszt:updated');$(arguments[0]).change();", combo);
		this.waitAjaxComplete();
	}
	
	public String getSelectedOptionText(WebElement combo){
		return (String) ((JavascriptExecutor) driver).executeScript("return $(arguments[0]).find('option:selected').text();", combo);
	}
	
	public void clickLink(WebElement element) {
		WebDriverWait wait = new WebDriverWait(driver, Utils.SELENIUM_WAIT_TIMEOUT * 1000);
		String url = element.getAttribute("href");
		checkNotNull(url, "Elemento " + element + " não possui atributo href.");

		JavascriptExecutor executor = (JavascriptExecutor) driver;
		executor.executeScript("arguments[0].click();", element);
		wait.until(Predicates.isPageLoaded(url));
		this.waitAjaxComplete();
		this.hasNoScriptErrors();
	}
	
	public WebElement find(WebElement el) throws Exception {
		for (int second = 0;; second++) {
		    if (second >= Utils.PAGE_ELEMENT_TIMEOUT) { 
		    	throw new Exception("Timeout trying to find WebElement");
		    }
		    try {
		    	if (el.isDisplayed()) {
		    		return el;
		    	}
		    } catch (Exception e) {}
		    Thread.sleep(1000);
		}
	}

	public void waitAjaxComplete() {
		WebDriverWait wait = new WebDriverWait(driver, 10);
		wait.until(Predicates.noActiveRequests(body));
		try {
			Thread.sleep(300);
		} catch (InterruptedException e) {
			fail("erro no thread sleep");
		}
	}
	
	public void hasNoScriptErrors() {
		String jsErrors = html.getAttribute("jserror");
		assertThat("Erros JS", jsErrors, isEmptyOrNullString());
	}
	
	public void hasErrors() {
		WebElement errorMessage = driver.findElement(By.cssSelector("div.alert.ng-isolate-scope.alert-danger"));
		assertTrue("Não possui mensagem de erro", !StringUtils.isEmpty(errorMessage.getText()));
	}

	public void hasNoErrors() {
		WebElement errorMessage = driver.findElement(By.id("errorPanel"));
		assertTrue("Mensagens de erro",  StringUtils.isEmpty(errorMessage.getText()));
	}

	public void hasSuccessMessage() {
		WebElement errorMessage = driver.findElement(By.cssSelector("div.alert.ng-isolate-scope.alert-success"));
		assertTrue("Não possui mensagem de sucesso", !StringUtils.isEmpty(errorMessage.getText()));
	}

	public void filtrar(String text) {
		filter.clear();
		this.type(filter, text);
		this.waitAjaxComplete();
	}

	public void detalharRegistro(String text) {
		this.filtrar(text);
		driver.findElement(By.cssSelector("button.glyphicon.glyphicon-edit")).click();
		this.waitAjaxComplete();
	}
	
	public void excluirRegistro(String text) {
		this.filtrar(text);
		driver.findElement(By.cssSelector("button.glyphicon.glyphicon-remove")).click();
		driver.findElement(By.cssSelector("div.modal-footer.ng-scope > button.btn.btn-default")).click();
		this.waitAjaxComplete();
	}
	
	public void pesquisarAutocomplete(WebElement campo, String valor) throws Exception {
		this.type(campo, valor);
		for (int second = 0;; second++) {
			if (second >= Utils.PAGE_ELEMENT_TIMEOUT) {
		    	throw new Exception("Timeout trying to pesquisarAutocomplete");
		    }
		    try {
		    	List<WebElement> listItems = driver.findElements(By.cssSelector("div.angucomplete-row.ng-scope"));
		    	if (listItems.size() > 0) {
		    		listItems.get(0).click();
		    		break;
		    	}
		    } catch (Exception e) {}
		    Thread.sleep(1000);
		}
	}
	
	public List<String> getMessages(){
		List<String> list = new ArrayList<String>();
		List<WebElement> msgs = driver.findElements(By.cssSelector(".ui-growl-message"));
		for (WebElement element : msgs) {
			list.add(element.getText());
		}
		return list;
	}
	
	public void assertMessage(String message){
		this.waitAjaxComplete();
		boolean exibiuMsg = false;
		for (String msg : getMessages()) {
			if(message.trim().equalsIgnoreCase(msg.trim())){
				assertEquals("Exibiu a mensagem: "+ message, msg , message);
				exibiuMsg = true;
				break;
			}
		}
		if(!exibiuMsg){
			assertTrue("Deveria ter exibido a mensagem: "+ message, exibiuMsg);
		}
	}
	
	public WebElement getAccordionByName(String name) {
		List<WebElement> home = driver.findElements(By.cssSelector(".ui-accordion-header a"));
		
    	WebElement tab = null;
    	for (WebElement webElement : home) {
    		if(webElement.getText().toLowerCase().indexOf(name) > -1){
    			tab = webElement;
    		}
		}
    	
    	return tab;
	}
	
	public WebElement getWebElementCombo(String valor) throws InterruptedException {
		return getWebElementCombo(1, valor);
		
	}
	
	public WebElement getWebElementCombo(int posicaoTelaCombo, String valor) throws InterruptedException {
		valor = valor.toLowerCase();
		WebElement pacote = null;
		WebElement combo = null;
		List<WebElement> combos = driver.findElements(By.cssSelector(".ui-selectonemenu-label"));
		combo = combos.get(--posicaoTelaCombo);
		combo.getText();
		Thread.sleep(100);
		combo.click();
		Thread.sleep(100);
		List<WebElement> gridProcedimentos = driver.findElements(By.cssSelector(".ui-selectonemenu-items li"));
		
		for (WebElement webElement : gridProcedimentos) {
			if(webElement.getText().toLowerCase().indexOf(valor) > -1){
				pacote = webElement;
				break;
    		}
		}
		
		if(pacote != null){
			pacote.click();
		}else{
			combo.click();
		}
		Thread.sleep(500);
		
		return pacote;
	}
	
}
